package yartiss.gui.graphics;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Paint;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Stroke;
import java.awt.image.BufferedImage;
import java.util.List;

import javax.swing.JComponent;
import javax.swing.JViewport;
import javax.swing.Scrollable;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;

import yartiss.engine.simulation.Event;
import yartiss.engine.simulation.IEventAddedListener;
import yartiss.service.model.IEventsModel;

@SuppressWarnings("serial")
public class JEnergyCurve extends JComponent implements Scrollable, IScreenShot{

	private final RenderingHints   hints  = new RenderingHints(null);
	private IEventsModel evenmodeltsModel;
	private int xUnit = 10;
	private int yUnit = 4;
	private int maxLevel;
	private int minLevel;
	private int margin = 50;
	private int xAlternate = 2;
	private int yAlternate = 10;
	private boolean isSlackEnergyCurveenabled = false;
	private boolean isSlackTimeCurveenabled = false;
	private boolean isEnergyLevelEnabled = true;

	/**
	 * @return the eventsModel
	 */
	public IEventsModel getEventsModel() {
		return evenmodeltsModel;
	}


	/**
	 * @param eventsModel the eventsModel to set
	 */
	public void setEventsModel(IEventsModel eventsModel) {
		unregisterListeners();
		this.evenmodeltsModel = eventsModel;
		registerListeners();
	}

	/**
	 * @return the minLevel
	 */
	public long getMinLevel() {
		return minLevel;
	}


	/**
	 * @param minLevel the minLevel to set
	 */
	public void setMinLevel(int minLevel) {
		this.minLevel = minLevel;
		draw = true;
		repaint();
		revalidate();
	}


	/**
	 * @return the maxLevel
	 */
	public long getMaxLevel() {
		return maxLevel;
	}


	/**
	 * @param maxLevel the maxLevel to set
	 */
	public void setMaxLevel(int maxLevel) {
		this.maxLevel = maxLevel;
		draw = true;
		repaint();
		revalidate();
	}

	/**
	 * 
	 * @param model
	 */
	public JEnergyCurve(IEventsModel model){
		this.setEventsModel(model);
		setOpaque(true);
		setBackground(Color.white);
	}

	private int offset = 0;
	private Point lastVpPos = new Point();

	private int translate(int x){
		if(getParent() instanceof JViewport){
			JViewport vp = (JViewport)getParent();
			Point pos = vp.getViewPosition();
			return  x - pos.x;
		}
		return x;
	}

	private BufferedImage image  = null;
	private boolean draw = false;
	@Override
	protected void paintComponent(Graphics g) {
		
		Rectangle size = g.getClipBounds();
		Dimension gsize = getSize();
		Point vpPos = lastVpPos;
		if(getParent() instanceof JViewport){
			JViewport vp = (JViewport)getParent();
			size = vp.getVisibleRect();
			vpPos = vp.getViewPosition();
		}
		if(lastVpPos.x != vpPos.x){
			offset += vpPos.x-lastVpPos.x;
			if(offset<0 || offset+size.width > image.getWidth()){
				draw = true;
				offset = 0;
			}
		}
		lastVpPos = new Point(vpPos);
		if(image ==null || image.getWidth() != size.width*3/2 || image.getHeight() != gsize.height){
			image = new BufferedImage(size.width*3/2, gsize.height, BufferedImage.TYPE_INT_ARGB);
			offset = 0;
			draw = true;
		}

		if(draw && evenmodeltsModel!=null){
			Graphics2D g2 = (Graphics2D)(image.getGraphics());
			hints.put( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
			hints.put( RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
			g2.setRenderingHints(hints);

			g2.setPaint(getBackground());
			g2.fillRect(0, 0, image.getWidth(), image.getHeight());

			paintLandMark(g2,translate(margin),  gsize.height-margin,getPreferredSize().width-2*margin, image.getWidth()-margin, gsize.height-2*margin);

			BasicStroke stroke=new BasicStroke(2);
			g2.setStroke(stroke);
			drawCurve(g2, translate(margin),  gsize.height-margin, vpPos.x, image.getWidth());
			draw = false;
			g2.dispose();
		}
		super.paintComponent(g);
		g.drawImage(image	 , vpPos.x-offset	, 0, image.getWidth()	 , image.getHeight()	, null);
		g.dispose();
	}



	private void drawGrid(Graphics2D g, int x0, int y0,int width,int vpWidth, int height){
		Color gridColor = Color.LIGHT_GRAY;
		int first = getXAlternate();
		if(x0<0){
			first = (-x0 / getXUnit());
			int diff = first % getXAlternate();
			if(diff!=0)
				first -= diff;
			first = first<0  ? 0 : first;
		}
		int hNb = vpWidth/getXUnit()+first;
		int vNb = maxLevel>0 ? maxLevel : height/getXUnit();
		for (int i = first; i < hNb; i += getXAlternate())
			drawLineWithColor(g, gridColor,x0+i*getXUnit(), y0, x0+i*getXUnit(),y0-getYUnit()*vNb-5 );
		for (int i = getyAlternate(); i <= vNb; i+=getyAlternate())
			drawLineWithColor(g, gridColor,x0, y0-i*getYUnit(), x0+width,y0-i*getYUnit() );
	}

	private void drawXAxe(Graphics2D g, int x0, int y0,int width, int vpWidth, int height ){

		int first = 0;
		if(x0<0){
			first = (-x0/getXUnit());
			first = first<0  ? 0 : first;
		}
		int hNb = vpWidth/getXUnit()+first;
		g.drawLine(x0, y0, x0+width, y0);
		for (int i = first; i < hNb; i++) {
			g.drawLine(x0+i*getXUnit(), y0+5, x0+i*getXUnit(),y0-5);
			if(i % getXAlternate()==0)
				drawString(g,i+"",x0+i*getXUnit(), y0+5 );
		}
	}

	private void drawYAxe(Graphics2D g, int x0, int y0, int height ){
		int vNb = maxLevel>0 ? maxLevel : height/getXUnit();
		drawLineWithColor(g,Color.black, x0, y0, x0, y0-getYUnit()*vNb-5 );
		for (int i = 0; i <= vNb; i++) {
			
			if(i%getyAlternate()==0){
				g.drawLine(x0-5, y0-i*getYUnit(), x0+5,y0-i*getYUnit());
				drawString(g,i+"",x0-15, y0-i*getYUnit()-10);
			}
		}
	}


	private void drawSlackTimeYAxe(Graphics2D g, int x0, int y0, int height) {
		
	}
	
	private void paintLandMark(Graphics2D g, int x0, int y0,int width,int vpWidth, int height ){
		g.setPaint(Color.black);
		int hNb = width/getXUnit();
		drawGrid(g, x0, y0,width,  width, height);
		drawXAxe(g, x0, y0, width, width, height);
		drawYAxe(g, x0, y0, height);
		if(isSlackTimeCurveenabled)drawSlackTimeYAxe(g, x0, y0, height);
		Stroke s = g.getStroke();
		g.setStroke(new BasicStroke(2));
		drawLineWithColor(g, Color.green,x0, y0-maxLevel*getYUnit(), x0+hNb*getXUnit(),y0-maxLevel*getYUnit() );
		drawLineWithColor(g, Color.red,x0, y0-minLevel*getYUnit(), x0+hNb*getXUnit(),y0-minLevel*getYUnit() );
		
		g.setStroke(s);
	}

	


	private void drawLineWithColor(Graphics2D g, Color c, int x1, int y1, int x2, int y2){
		Paint p = g.getPaint();
		g.setPaint(c);
		g.drawLine(x1, y1, x2, y2);
		g.setPaint(p);
	}

	private void drawCurve(Graphics2D g, int x0, int y0, int vpPos, int width){
		List<Event> events = evenmodeltsModel.getAllEvents();
		if(events.isEmpty())return ;
		long lastDate = events.get(events.size()-1).getDate();
		Dimension old = getPreferredSize();
		if(old.width != 2*margin+getXUnit()*lastDate || maxLevel*yUnit+2*margin != old.height){
			old.width = (int) (2*margin+getXUnit()*lastDate+70);
			old.height = 2*margin+getYUnit()*maxLevel;
			if(old.height>1000)old.height = 1000;
			setPreferredSize(old);
			draw = true;
			revalidate();
		}

		Event firstEvent = events.get(0);
		Point p1E = new Point(), p2E = new Point(), tmp;
		p1E.setLocation(x0+firstEvent.getDate()*getXUnit(),y0  - firstEvent.getMetric(Event.EnergyLevel)*getYUnit());
		
		Event firstSEEvent = firstSlackEnergyDefined(events);
		Point p1SE = new Point(), p2SE = new Point();
		if(firstSEEvent != null)
			p1SE.setLocation(x0+firstSEEvent.getDate()*getXUnit(),y0 - firstSEEvent.getMetric(Event.SlackEnergy)*getYUnit());
		
		Event firstSTEvent = firstSlackTimeDefined(events);
		Point p1ST = new Point(), p2ST = new Point();
		if(firstSTEvent != null)
			p1ST.setLocation(x0+firstSTEvent.getDate()*getXUnit(),y0 - firstSTEvent.getMetric(Event.SlackTime)*getYUnit());

		
		for (Event event : events) {
			if(isEnergyLevelEnabled){
				p2E.setLocation(x0+event.getDate()*getXUnit(), y0 - event.getMetric(Event.EnergyLevel)*getYUnit());
				drawSegment(p1E, p2E, g, Color.blue);
				tmp = p1E;
				p1E=p2E;
				p2E = tmp;
			}
			if(isSlackEnergyCurveenabled && event.getMetric(Event.SlackEnergy) != Event.Undefined){
				p2SE.setLocation(x0+event.getDate()*getXUnit(), y0 - event.getMetric(Event.SlackEnergy)*getYUnit());
				drawSegment(p1SE, p2SE, g, Color.orange);
				tmp = p1SE;
				p1SE=p2SE;
				p2SE = tmp;
			}
			
			if(isSlackTimeCurveenabled &&  event.getMetric(Event.SlackTime) != Event.Undefined){
				p2ST.setLocation(x0+event.getDate()*getXUnit(), y0 - event.getMetric(Event.SlackTime)*getYUnit());
				drawSegment(p1ST, p2ST, g, Color.magenta);
				tmp  = p1ST;
				p1ST = p2ST;
				p2ST = tmp;
			}
		}
	}
	
	
	private Event firstSlackEnergyDefined(Iterable<Event> events){
		for (Event event : events) {
			if(event.getMetric(Event.SlackEnergy) != Event.Undefined)return event;
		}
		return null;
	}
	
	private Event firstSlackTimeDefined(Iterable<Event> events){
		for (Event event : events) {
			if(event.getMetric(Event.SlackTime) != Event.Undefined)return event;
		}
		return null;
	}
	
	private void drawSegment(Point p1, Point p2, Graphics2D g, Color color){
		Paint oldColor = g.getPaint();
		g.setPaint(color);
		g.drawLine(p1.x, p1.y, p2.x, p2.y);

		Stroke s = g.getStroke();
		g.setStroke(new BasicStroke(7));
		g.drawLine(p1.x, p1.y, p1.x, p1.y);
		
		g.setStroke(s);
		g.setPaint(oldColor);
		//drawString(g, event.toString(),p1.x, p1.y);
	}

	private  void drawString( Graphics2D g2, String str,int x, int y) {
		g2.setPaint(Color.black);
		FontMetrics metric = g2.getFontMetrics();
		int h = metric.getAscent();
		int n = str.length()>1 ? 2 : 4;
		g2.drawString(str,x-h/n,y+h);
	} 

	/**
	 * 
	 */
	private IEventAddedListener eventListener = new IEventAddedListener() {
		@Override
		public void perfomredAction(Event e) {
			draw = true;
			repaint();
		}
	};


	/**
	 * 
	 */
	private void registerListeners(){
		if(evenmodeltsModel != null)
			evenmodeltsModel.addEventAddedListener(eventListener);
	}

	/**
	 * 
	 */
	private void unregisterListeners(){
		if(evenmodeltsModel != null)
			evenmodeltsModel.removeEventAddedListener(eventListener);
	}

	@Override
	public Dimension getPreferredScrollableViewportSize() {
		Dimension d = new Dimension(5000, 5000);
		return d;
	}


	@Override
	public int getScrollableBlockIncrement(Rectangle visibleRect,int orientation, int direction) {
		switch(orientation) {
		case SwingConstants.VERTICAL:
			return visibleRect.height;
		case SwingConstants.HORIZONTAL:
			return visibleRect.width;
		default:
			throw new IllegalArgumentException("Invalid orientation: " + orientation);
		}
	}


	@Override
	public boolean getScrollableTracksViewportHeight() {
		Container parent = SwingUtilities.getUnwrappedParent(this);
		if (parent instanceof JViewport) {
			return parent.getHeight() > getPreferredSize().height;
		}
		return false;
	}


	@Override
	public boolean getScrollableTracksViewportWidth() {
		Container parent = SwingUtilities.getUnwrappedParent(this);
		if (parent instanceof JViewport) {
			return parent.getWidth() > getPreferredSize().width;
		}
		return false;
	}


	@Override
	public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) {
		switch(orientation) {
		case SwingConstants.VERTICAL:
			return visibleRect.height / 10;
		case SwingConstants.HORIZONTAL:
			return visibleRect.width / 10;
		default:
			throw new IllegalArgumentException("Invalid orientation: " + orientation);
		}
	}


	public void setXUnit(int value) {
		xUnit = value;
		draw = true;
		repaint();
		revalidate();
	}

	public void setYUnit(int value) {
		yUnit = value;
		draw = true;
		repaint();
		revalidate();
	}

	/**
	 * @return the yUnit
	 */
	public int getYUnit() {
		return yUnit;
	}


	/**
	 * @return the xUnit
	 */
	public int getXUnit() {
		return xUnit;
	}


	/**
	 * @return the xAlternate
	 */
	public int getXAlternate() {
		return xAlternate;
	}


	/**
	 * @param xAlternate the xAlternate to set
	 */
	public void setXAlternate(int xAlternate) {
		this.xAlternate = xAlternate;
		draw = true;
		repaint();
	}


	/**
	 * @return the yAlternate
	 */
	public int getyAlternate() {
		return yAlternate;
	}


	/**
	 * @param yAlternate the yAlternate to set
	 */
	public void setyAlternate(int yAlternate) {
		this.yAlternate = yAlternate;
		draw = true;
		repaint();
	}


	@Override
	public Image getScreenShot() {
		return image;
	}


	/**
	 * @return the isSlackEnergyCurveenabled
	 */
	public boolean isSlackEnergyCurveenabled() {
		return isSlackEnergyCurveenabled;
	}


	/**
	 * @param isSlackEnergyCurveenabled the isSlackEnergyCurveenabled to set
	 */
	public void setSlackEnergyCurveEnabled(boolean isSlackEnergyCurveenabled) {
		this.isSlackEnergyCurveenabled = isSlackEnergyCurveenabled;
		draw = true;
		repaint();
	}


	/**
	 * @return the isSlackTimeCurveenabled
	 */
	public boolean isSlackTimeCurveenabled() {
		return isSlackTimeCurveenabled;
	}


	/**
	 * @param isSlackTimeCurveenabled the isSlackTimeCurveenabled to set
	 */
	public void setSlackTimeCurveEnabled(boolean isSlackTimeCurveenabled) {
		this.isSlackTimeCurveenabled = isSlackTimeCurveenabled;
		draw = true;
		repaint();
	}


	/**
	 * @return the isEnergyLevelEnabled
	 */
	public boolean isEnergyLevelEnabled() {
		return isEnergyLevelEnabled;
	}


	/**
	 * @param isEnergyLevelEnabled the isEnergyLevelEnabled to set
	 */
	public void setEnergyLevelEnabled(boolean isEnergyLevelEnabled) {
		this.isEnergyLevelEnabled = isEnergyLevelEnabled;
		draw = true;
		repaint();
	}
}
